webpack 基础使用

webpack 简介

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

webpack

功能进化

  • Webpack V1(2014.2.20)
    • 编译、打包
    • HMR(模块热更新)
    • 代码分割
    • 文件处理(loader、plugin)
  • Webpack V2(2017.1.18)
    • Tree Shaking(在项目中没有实际运用的代码会被删除,打包体积更小)
    • ES module
    • 动态Import
  • Webpack V3(2017.6.19)
    • Scope Hoisting(作用域提升)
    • Magic Comments(配合动态import使用)
  • Webpack V4(2018.2.27)
    • webpack 4 更快(速度提升98%)!
    • Mode, 零配置以及默认值
    • 再见 CommonsChunkPlugin
    • WebAssembly 支持
    • 模块类型简介以及 .mjs 支持

核心概念

Entry 代码的入口,打包入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一个入口
module.exports = {
entry: 'index.js'
};
// 推荐写法
module.exports = {
entry: {
index: 'index.js'
}
};
// 多个入口
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
}

Output 打包输出

1
2
3
4
5
6
7
8
9
10
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js', // 打包之后的文件名 [name]就对应entry里面的key值。
path: __dirname + '/dist' // 打包输出文件路径
}
}

Loaders 处理css img 等各种其他文件

1
2
3
4
5
6
7
8
9
// 单个loader
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' }
]
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 多个loader
module.exports = {
module: {
rules: [
{
test: /\.css$/, // 正则匹配css文件
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: { // loader配置
modules: true
}
}
]
}
]
}
}

Plugins 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const path = require('path');

const config = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'}) // 根据`./src/index.html`生成一个首页,会引入打包的js、css文件
]
};

module.exports = config;

使用 webpack

webpack 默认打包js,官网

编译打包ES6

需要两个loader:npm i babel-loader babel-core --save-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
entry: {
app: 'app.js'
},
output: {
filename: '[name].[hash:8].js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: '/node_modules/' // 将node_module中的文件排除在外,因为已经是编译过的
}
]
}
}

{ test: Condition }:匹配特定条件。一般是提供一个正则表达式或正则表达式的数组,但这不是强制的。

{ include: Condition }:匹配特定条件。一般是提供一个字符串或者字符串数组,但这不是强制的。

{ exclude: Condition }:排除特定条件。一般是提供一个字符串或字符串数组,但这不是强制的。

{ and: [Condition] }:必须匹配数组中的所有条件

{ or: [Condition] }:匹配数组中任何一个条件

{ not: [Condition] }:必须排除这个条件

Babel Presets

虽然引入了 babel-loader,但是它并不知道是根据什么规范来打包的,这个时候就需要配置一个Babel Presets(预设):npm i babel-preset-env --save-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: { // options 属性为字符串或对象。值可以传递到 loader 中,将其理解为 loader 选项。
presets: ['babel-preset-env']
}
},
exclude: '/node_modules/' // 将node_module中的文件排除在外,因为已经是编译过的
}
]
}
}

也可以新建.babelrc文件,上面是直接将presets设置在loader中的,也可以单独写在.babelrc文件中,babel会自动读取,具体请查看babel

Babel Polyfill

安装: npm install --save-dev babel-polyfill

使用 babel-polyfill: import 'babel-polyfill'

Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。在这里可以说是为了使用某个浏览器或者其他执行环境不支持的函数或者对象能够使用而添加的原型方法,或者第三方库

例如:

我们想要使用 es2015 的语法中的某些新的对象方法或者数据类型,就需要添加 babel-polyfill,例如 Array.from 方法很多浏览器不支持,你就需要垫片来提高兼容性。

为了在版本低浏览器中能够使用 promise,我们需要提前执行一个 promise 文件,以便能够在全局中使用。

babel-runtime

安装: npm i --save-dev babel-runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var obj = _defineProperty({}, 'name', 'JavaScript');

类似上面的帮助函数 _defineProperty 可能会重复出现在一些模块里,导致编译后的代码体积变大。Babel 为了解决这个问题,提供了单独的包 babel-runtime 供编译模块复用工具函数。

安装: npm i babel-plugin-transform-runtime --save-dev

在.babelrc文件加入配置:

1
2
3
4
5
6
{
"presets": [
["babel-preset-env"]
],
"plugins": ["transform-runtime"]
}

启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数,转译代码如下:

1
2
3
4
5
6
'use strict';
// 之前的 _defineProperty 函数已经作为公共模块 `babel-runtime/helpers/defineProperty` 使用
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var obj = (0, _defineProperty3.default)({}, 'name', 'JavaScript');

除此之外,babel 还为源代码的非实例方法(Object.assign,实例方法是类似这样的 “foobar”.includes(“foo”))和 babel-runtime/helps 下的工具函数自动引用了 polyfill。这样可以避免污染全局命名空间,非常适合于 JavaScript 库和工具包的实现。例如 const obj = {}, Object.assign(obj, { age: 30 }); 转译后的代码如下所示:

1
2
3
4
5
6
7
8
9
'use strict';
// 使用了 core-js 提供的 assign
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var obj = {};
(0, _assign2.default)(obj, {
age: 30
});

打包Typescript

安装 typescript-loader: npm i typescript ts-loader --save-dev 或者第三方loader:npm i typescript awesome-typescript-loader --save-dev

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
entry: {
'app': './src/app.ts'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: {
test: /\.tsx?$/,
use: {
loader: 'ts-loader'
}
}
}
}

tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions" : {
"module": "commonjs",
"target": "es5", // 将ts编译成es5语法
"allowJs": true // 是否允许出现js语法
},
"include": {
"./src/*"
},
"exclude": {
"./node_module"
}
}

处理CSS

安装 css-loader 和 style-loader // 在页面中插入style标签:

npm i style-loader css-loader --save-dev

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
entry: {
'app': './src/app.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: {
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
]
}
}
}

配置Less/Sass

安装:

npm i less-loader less --save-dev

npm i sass-loader node-sass --save-dev

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
entry: {
'app': './src/app.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: {
test: /\.less$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'less-loader'
}
]
}
}
}

提取CSS

安装插件:npm i extract-text-webpack-plugin --save-dev

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin);
module.exports = {
entry: {
'app': './src/app.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: {
test: /\.less$/,
use: ExtractTextWebpackPlugin.extract({
fallback: {
loader: 'style-loader'
},
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options:{
minimize: true // 压缩
}
},
{
loader: 'less-loader'
}
]
})
}
},
plugins:[
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false
// allChunks默认false,只打包初始化的css,异步加载的css不会打包
})
]
}

PostCss

PostCSS是一个使用JS插件转换样式的工具。

PostCss 相关联的插件还有: Autoprefixer、CSS-nano、CSS-next等

  • Autoprefixer 可以为我们的 CSS 加上各个浏览器的前缀,我们只需要写标准的就好
  • CSS-nano 可以压缩我们的 CSS,在 CSS-loader 中内嵌的 CSS 压缩就是采用该方式
  • CSS-next 可以让我们现在使用未来的 CSS 新语法

使用这些功能需要安装:

1
npm install postcss postcss-loader autoprefixer cssnano postcss-cssnext --save-dev

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
'app': './src/app.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: {
test: /\.less$/,
use: ExtractTextWebpackPlugin.extract({
fallback: {
loader: 'style-loader'
},
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
minimize: true // 压缩
}
},
{ //在预编译语言sass/less等之前,css-loader之后使用
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')(), //插件调用autoprefixer
require('postcss-cssnext')() //插件调用cssnext
]
}
},
{
loader: 'less-loader'
}
]
})
}
},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false
// allChunks默认false,只打包初始化的css,异步加载的css不会打包
})
]
}

文件处理

图片处理

使用场景:

  • CSS中引入的图片 —— file-loader
  • 自动合成雪碧图 —— postcss-sprites
  • 压缩图片 —— img-loader
  • Base64编码 —— url-loader

安装:npm i file-loader url-loader img-loader postcss-sprites --save-dev

url-loader(urL-loader 有file-loader的功能,可以只用url-loader):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: [{
loader: 'url-loader', //
options: {
publicPath: '',
outputPath: 'dist/', // 设置输出文件地址
useRelativePath: true,
limit: 10000 // 10k
}
}]
}]
}

img-loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: [{
loader: 'url-loader',
options: {
name: '[name]-[hash:5].min.[ext]', // 5位hash值
publicPath: '',
outputPath: 'dist/', // 设置输出文件地址
useRelativePath: true,
limit: 10000 // 10k
}
},
{
loader: 'img-loader'
options:{
pngquant:{
quality: 80 //调整图片质量
}
}
}]
}]
}

postcss-sprites 合成雪碧图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: [{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('postcss-sprites')({ //插件调用
spritePath: 'dist/assets/imgs/sprites', //设置生成图片位置
retina: true // 1@2x.png 图片名字命名加 @2x
}),
require('postcss-cssnext')()
]
}
}]
}]
}

注: 还可以在 url-loader 的 options 设置 name 来修改合成后图片的名字:

1
2
3
4
5
6
7
8
9
10
{
loader: 'url-loader', //
options: {
name: '[name].min.[ext]', //修改合成后图片的名字
publicPath: '',
outputPath: 'dist/', // 设置输出文件地址
useRelativePath: true,
limit: 10000 // 10k
}
}

字体文件处理

1
2
3
4
5
6
7
8
9
10
11
12
module: {
rules: [{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
name: 'resource/[name].[ext]'
}
}]
}]
}

处理第三方js库

1
2
3
4
5
6
new webpack.ProvidePlugin({ //jQuery的JS文件在本地加载之后不能直接使用,需要如下配置
jQuery: "jquery",
$: "jquery"
}),

需要注意的是 如果是 cdn 加载第三方库是可以直接使用的

或者在 module 中使用 imports-loader :

安装: npm i imports-loader --save-dev

1
2
3
4
5
6
7
8
9
10
11
{
test:path.resolve(__dirname,'src/app.js'),
use:[
{
loader: 'imports-loader',
options:{
$:'jquery'
}
}
]
}

生成html

利用 html-webpack-plugin 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins:[ //插件
new HtmlWebpackPlugin({
filename: 'index.html', //入口文件
template: './src/index.html', //模板
chunks:['index','vendor'], //该html内引用的 chunks js文件
minify:{
caseSensitive: false, //是否大小写敏感
removeComments:true, // 去除注释
removeEmptyAttributes:true, // 去除空属性
collapseWhitespace: true //是否去除空格
}
})
]
...

html内图片处理

配置用 html-withimg-loader

1
2
3
4
5
6
7
8
9
10
...
{
test: /\.html$/,//html内图片处理
use: [
{
loader: 'html-withimg-loader',
}
]
}
...



完~